package com.demo.common.lock.aspectj;

import cn.hutool.core.lang.Opt;
import com.demo.common.lock.annotation.RedisLock;
import com.demo.common.lock.utils.AspectUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * 分布式锁注解切面
 *
 * @author ROG
 * @formatter:on
 * @since 2023/11/29
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class LockAspect {

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 拦截被@RedisLock注解的方法
     *
     * @param joinPoint
     * @param redisLock
     * @return java.lang.Object
     * @author ROG
     * @since 2024/3/29 - 10:45
     */
    @Around(value = "@annotation(redisLock)")
    public Object around(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
        // 根据注解参数生成redis key
        String key = generateKey(joinPoint, redisLock);
        // 获取锁
        RLock lock = redissonClient.getLock(key);
        if (lock.tryLock(redisLock.waitTime(), redisLock.leaseTime(), TimeUnit.MILLISECONDS)) {
            try {
                return joinPoint.proceed();
            } finally {
                // 方法执行完成需要立即释放锁，判断是否当前线程持有锁
                if (lock.isLocked()) {
                    lock.unlock();
                }
            }
        } else {
            throw new RuntimeException("获取锁失败，方法被占用");
        }
    }

    /**
     * 根据注解参数生成redis key
     *
     * @param joinPoint
     * @param redisLock
     * @return java.lang.String
     * @author ROG
     * @since 2024/3/29 - 10:49
     */
    private String generateKey(ProceedingJoinPoint joinPoint, RedisLock redisLock) {
        String key = AspectUtil.buildTemplate(joinPoint, redisLock.key(), redisLock.args());
        return Opt.ofBlankAble(key).orElseThrow(() -> new RuntimeException("redis key 不能为空"));
    }
}
